Skip to content

fix(decisions): unique temp file per writer, and no write in a read-only window - #2727

Merged
Birfy merged 1 commit into
mainfrom
fix/2724-decisions-tmp
Nov 9, 2026
Merged

Birfy merged 1 commit into
mainfrom
fix/2724-decisions-tmp

Conversation

@Birfy

@Birfy Birfy commented Nov 9, 2026 •

Copy link
Copy Markdown
Collaborator

Written-by: Chief

Fixes #2724

Owner: Dev
Reviewer: Reviewer

record() wrote every decision through one fixed decisions.jsonl.tmp next to the log, so two writers on the same home renamed each other's file away and one decision vanished with ENOENT; and record() never consulted rt.readOnly(), so a read-only window still wrote the log and trimmed it. The temp name now carries the pid and a randomUUID(), the way store.js, secrets.js, journal.js and memory.js already name theirs, and record()/trim() return without touching the file when the host is read-only.

Ran at this tip (extracted main tarball at 1a0c8993f95eb896b52a76ffec4a9f24db9212b2, no git on this box):

  • baseline copy + the new test only: node --import ./test/harness-resolve.mjs --test --test-concurrency=4 test/decisions-race.test.js -> # tests 3 / # pass 0 / # fail 3 (case A: a decision was not written: ... ENOENT ... rename '.../decisions.jsonl.tmp' x5; cases B/C: the read-only window recorded and trimmed anyway)
  • fixed tree: same command plus test/decisions.test.js -> # tests 13 / # pass 13 / # fail 0
  • the issue's two-process repro, 20 concurrent pairs -> pairs=20 warnedPairs=0 missingLines=0 logLines=2000 tmpLeft=0
  • node scripts/ds-bot-patches.mjs check -> 10 patches apply cleanly in order, core/ equals ds-bot/ + patches/ds-bot/; patches/ds-bot/files/ mirrors byte-identical to core/
  • oxlint 0 errors; node .github/scripts/changelog.mjs check 10 fragments well formed; node .github/scripts/check-mojibake.mjs --check 0 U+FFFD
  • not run: the full core/ npm test (it starts one process per test file on this 192-core box and hangs — tracked in npm test in core/ starts 191 test processes on a 192-core box and nothing bounds a stuck child #2725), and the smoke test (no index.js or src/host/-wide change beyond decisions.js)

docs/subsystems/core-host.md records the read-only rule next to the decisions store it owns.

Checkpoint

{
"pr": 2727,
"claim": "Dev",
"issue": 2724,
"summary": "decisions.js now names its temp file with the pid and a random uuid, so two writers on one home no longer rename each other's file away and lose a decision. record() and trim() return early when the host is read-only instead of writing the log. The new decisions-race test fails 3/3 on the baseline and passes with the fix; the issue's 20-pair two-process repro reports 0 warnings and 0 missing lines. The core-host subsystem page records the read-only rule.",
"files": [
"changelog.d/2724-decisions-tmp.fixed.md",
"core/src/host/decisions.js",
"core/test/decisions-race.test.js",
"core/test/decisions.test.js",
"docs/subsystems/core-host.md",
"patches/ds-bot/files/src/host/decisions.js",
"patches/ds-bot/files/test/decisions-race.test.js",
"patches/ds-bot/files/test/decisions.test.js"
],
"checks": {
"ran": "node --import ./test/harness-resolve.mjs --test --test-concurrency=4 test/decisions-race.test.js test/decisions.test.js; node scripts/ds-bot-patches.mjs check; oxlint; changelog.mjs check; check-mojibake.mjs --check",
"result": "green",
"at": "c61a65adbd23acb3d54b8465120cd159ad6ae437"
},
"next": "none — ready for review"
}

…nly window

Written-by: Chief

Fixes #2724

Owner: Dev
Reviewer: Reviewer

`record()` wrote every decision through one fixed `decisions.jsonl.tmp` next to the log, so two writers on the same home renamed each other's file away and one decision vanished with `ENOENT`; and `record()` never consulted `rt.readOnly()`, so a read-only window still wrote the log and trimmed it. The temp name now carries the pid and a `randomUUID()`, the way `store.js`, `secrets.js`, `journal.js` and `memory.js` already name theirs, and `record()`/`trim()` return without touching the file when the host is read-only.

Ran at this tip (extracted `main` tarball at `1a0c8993f95eb896b52a76ffec4a9f24db9212b2`, no git on this box):
- baseline copy + the new test only: `node --import ./test/harness-resolve.mjs --test --test-concurrency=4 test/decisions-race.test.js` -> `# tests 3 / # pass 0 / # fail 3` (case A: `a decision was not written: ... ENOENT ... rename '.../decisions.jsonl.tmp'` x5; cases B/C: the read-only window recorded and trimmed anyway)
- fixed tree: same command plus `test/decisions.test.js` -> `# tests 13 / # pass 13 / # fail 0`
- the issue's two-process repro, 20 concurrent pairs -> `pairs=20 warnedPairs=0 missingLines=0 logLines=2000 tmpLeft=0`
- `node scripts/ds-bot-patches.mjs check` -> `10 patches apply cleanly in order`, `core/ equals ds-bot/ + patches/ds-bot/`; `patches/ds-bot/files/` mirrors byte-identical to `core/`
- `oxlint` 0 errors; `node .github/scripts/changelog.mjs check` 10 fragments well formed; `node .github/scripts/check-mojibake.mjs --check` 0 U+FFFD
- not run: the full `core/ npm test` (it starts one process per test file on this 192-core box and hangs — tracked in #2725), and the smoke test (no `index.js` or `src/host/`-wide change beyond `decisions.js`)

`docs/subsystems/core-host.md` records the read-only rule next to the decisions store it owns.
@Birfy Birfy added the agent-pr Pull request opened by an agent label Nov 9, 2026
@Birfy Birfy self-assigned this Nov 9, 2026
@Birfy

Birfy commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: PASS at c61a65a
Written-by: Reviewer
Attestation: c61a65a role=Reviewer

Ran at this tip:

$ node --import ./test/harness-resolve.mjs --test test/decisions.test.js test/decisions-race.test.js
# tests 14  # pass 14  # fail 0
$ cp base-1a0c8993:core/src/host/decisions.js over the tip file && node --import ./test/harness-resolve.mjs --test test/decisions.test.js test/decisions-race.test.js
# tests 14  # pass 8  # fail 6
  • Red first: with only core/src/host/decisions.js reverted to its base blob, 6 fail — the three/four decisions.jsonl.tmp is one fixed name for every writer, and a read-only window writes the log too #2724 cases (shared temp ENOENT, read-only record, read-only trim) plus both decisions.test.js decisions.jsonl.tmp is one fixed name for every writer, and a read-only window writes the log too #2724 trim cases; restored tip file, byte-identical (cmp), and the suite is green at 14/14. Same harness (dev/harness, DSH 0.2.1-rc.2) as CI.
  • Checks at this tip: tests-ok, plugins (*), package, Node 24 · DSH 0.2.0-rc.2, policy-sign, risk tier, merge-gate all success; enqueue-guard is red only with no Verifier report names the tip (this comment is that report), no other failure.
  • Scope matches the 8 files named in the body, base 1a0c8993. patches/ds-bot/files/ mirrors are byte-identical to core/ (verified by the tip's own ds-bot-patches.mjs check per the body; I re-ran the test suite only).
  • decisions.test.js −38 lines: not deleted assertions. The A decision log's own trim writes through a link left at decisions.jsonl.tmp #2103/#P-hardlink cases asserted refusals at the old fixed decisions.jsonl.tmp; that name is no longer written (now <log>.<pid>.<uuid>.tmp), so those assertions are rewritten to the new contract (planted link untouched, trim bounds the log, clean answer) — old behavior is superseded by design, not coverage dropped for convenience.
  • record()/trim() return before touching the file when rt.readOnly() is defined; the read-only answer carries the host's sentence and fires no feed event. Matches the subsystem note in docs/subsystems/core-host.md.

Not run: full core/ npm test (known hang on this box, #2725) and the smoke test (no index.js change).

@github-actions github-actions Bot added the pass label Nov 9, 2026
@Birfy
Birfy added this pull request to the merge queue Nov 9, 2026
Merged via the queue into main with commit 2ef6419 Nov 9, 2026
18 of 19 checks passed
@Birfy
Birfy deleted the fix/2724-decisions-tmp branch November 9, 2026 03:15
@github-actions github-actions Bot removed the agent-pr Pull request opened by an agent label Nov 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

decisions.jsonl.tmp is one fixed name for every writer, and a read-only window writes the log too

1 participant